package cn.mengfly.testnrz.curator;

/**
 * Twitter 的SnowFlake id生成算法，生成的Id结构如下：
 * 1. 长度为64bit
 * 2. 分为4个部分（不用（1bit）+时间戳（41bit）+设备id（10bit）+序列号（12bit））
 * 3. 时间戳比特位可以精确到毫秒
 * 4. 如果按照上面的方式生成id，则可以使用69年，每毫秒可以容纳1024*4096个id
 * <p>
 * 其中后面三个部分的比特位可以根据实际情况做稍微的调整
 *
 * @author Mengfly
 */
public class SnowflakeIdGenerator {
    /**
     * 开始使用算法的时间：2017-01-01 00:00:00
     */
    public static final long START_TIME = 1483200000000L;

    /**
     * worker id 的 bit 数，最多支持 8192 个节点
     */
    private static final int WORKER_ID_BITS = 13;

    /**
     * 序列号，支持单节点最高每毫秒最大Id数为1024
     */
    private static final int SEQUENCE_BITS = 10;

    /**
     * 最大的worker id， 8091
     * <p>
     * -1L 的补码（二进制的所有位均为1(65个1)）
     * 右移13位以后后面的13位就会变成0，然后取反就会剩下13个1
     */
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);

    /**
     * 最大序列号
     */
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);

    /**
     * worker 节点编号的移位
     */
    private static final long APP_HOST_ID_SHIFT = SEQUENCE_BITS;

    /**
     * 时间戳的移位
     */
    private static final long TIMESTAMP_LEFT_SHIFT = WORKER_ID_BITS + APP_HOST_ID_SHIFT;

    /**
     * 该项目的worker节点Id
     */
    private long workerId;
    /**
     * 上次生成Id的时间戳
     */
    private long lastTimestamp = -1L;
    /**
     * 当前毫秒生成的序列号
     */
    private long sequence = 0L;

    /**
     * 生成下一个Id
     *
     * @return 下一个Id
     */
    public Long nextId() {
        return generateId();
    }

    private synchronized long generateId() {
        long current = System.currentTimeMillis();
        if (current < lastTimestamp) {
            // 如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过，出现问题返回-1
            return -1;
        }
        if (current == lastTimestamp) {
            // TODO 这里的代码是有问题的，源码中是将 sequence = (sequence + 1) & MAX_SEQUENCE 放在这里的
            // 但是如果放在了这里，在sequence == MAX_SEQUENCE的时候，时间戳是更新了，但是sequence并没有变为0
            // 这样不会引起主键冲突，但会导致另一个问题，也就是主键的顺序性就没有办法保证了吧
            if (sequence == MAX_SEQUENCE) {
                // 当前毫秒生成的序列数已经大于最大值，那么阻塞到下一个毫秒再获取新的时间戳
                current = this.nextMs(lastTimestamp);
            }
            sequence = (sequence + 1) & MAX_SEQUENCE;
        } else {
            // 当前的时间戳已经是下一个毫秒
            sequence = 0L;
        }
        // 更新上一次生成的ID的时间戳
        lastTimestamp = current;
        // 进行移位操作，生成int64的唯一Id
        // 移动时间戳
        long time = (current - START_TIME) << TIMESTAMP_LEFT_SHIFT;
        // 移动设备Id
        long workerId = this.workerId << APP_HOST_ID_SHIFT;

        return time | workerId | sequence;
    }

    /**
     * 阻塞到下一个毫秒
     *
     * @param lastTimestamp timestamp
     * @return 下一个毫秒
     */
    private long nextMs(long lastTimestamp) {
        long current = System.currentTimeMillis();
        while (current <= lastTimestamp) {
            current = System.currentTimeMillis();
        }
        return current;
    }

    public static long getTimestamp(long id) {
        return START_TIME + (id >> (TIMESTAMP_LEFT_SHIFT));
    }

    public static long getWorkerId(long id) {
        return (id >> SEQUENCE_BITS) & (~(-1L << WORKER_ID_BITS));
    }

    public static long getSequence(long id) {
        return id & (~(-1L << SEQUENCE_BITS));
    }


    private static final SnowflakeIdGenerator INSTANCE = new SnowflakeIdGenerator();

    public static SnowflakeIdGenerator inst() {
        return INSTANCE;
    }

    public synchronized void init(long workerId) {
        if (workerId > MAX_WORKER_ID) {
            // Zookeeper分配的workerId过大
            throw new IllegalArgumentException("worker Id wrong:" + workerId);
        }
        INSTANCE.workerId = workerId;
    }

    private SnowflakeIdGenerator() {
    }

}
